#include "findMisMatch_gpu.h"
#include "../CompressAlign/cpp/mian_util.h"
#include "../CompressAlign/cpp/compress_gpu.h"
#include "../CompressAlign/cpp/compressTitle_cpu.h"
#include "../cpp/cuda_header.h"
#define CHAR_PER_WORD 16
char getChar(int i)
{
	switch(i)
	{
		case(0):
			return 'A';
			break;
		case(1):
			return'C';
			break;
		case(2):
			return'G';
			break;
		case(3):
			return'T';
			break;
	}
}

char getComplement(char base) {

	switch(base) {
		case 'a':
			return 't';
		case 'A':
			return 'T';
		case 't':
			return 'a';
		case 'T':
			return 'A';
		case 'c':
			return 'g';
		case 'C':
			return 'G';
		case 'g':
			return 'c';
		case 'G':
			return 'C';
		default: {
				 printf("character : %c \n", base);
				 ERROR_EXIT("unknown base");
			 }
	}
}
void reverse_complement(char* outRead, const char* inRead, const uint32 readLength) {
	for(int i = 0; i < readLength; i++) {
		outRead[i] = getComplement(inRead[i]);
	}
	reverse(outRead, outRead + readLength);
}

void getReverseBuffer(char* inBuffer, uint32 readNum, uint32 readLength, char* outBuffer)
{
	int i;
	for(i=0; i<readNum; i++) {
		reverse_complement(outBuffer + i*readLength, inBuffer + i*readLength, readLength);
	}
/*
	for(i = 0; i < readLength; i++) {
		printf("%c", inBuffer[i]);
	}
	printf("\n");

	for(i = 0; i < readLength; i++) {
		printf("%c", outBuffer[i]);
	}
	printf("\n");
*/
}

void decompressDNABuffer(uint32* packed, uint32 readNum,uint32 readLength, char* unpacked)
{
	int i,j,k,l;
	int querySizeInWord = (readLength + CHAR_PER_WORD - 1)/ CHAR_PER_WORD; 
	uint32 temp;
	for(i=0; i<readNum; i++) {
		k = 0;
		for(j=0; j<querySizeInWord && k < readLength; j++) {
			temp = packed[i*querySizeInWord + j]; 
			for(l=0; l<CHAR_PER_WORD && k < readLength; l++) {
				unpacked[i * readLength + k] = getChar((temp & 0xC0000000) >> 30);
				temp <<= 2;
				k++;
			}
		}
	}
}

char* getTitle(char* titleBuffer, uint32 readNameLength ,uint32 queryId)
{
	return titleBuffer + readNameLength * queryId;
}

void getQuery(char* queryBuffer, uint32 queryLen, uint32 queryId, char* query)
{
	int i;
	for(i=0; i<queryLen; i++)
		query[i] = queryBuffer[queryId * queryLen + i];
	query[i] = '\0';
}

void decompressQueryId(uint32* in, uint32* in_len, uint32 dataSize, uint32* out, uint32* outSize)
{
	uint32 maxOutSize = (*outSize);
	uint32* d_out, *d_in, *d_in_len;
	GPUMALLOC((void**)&d_out, maxOutSize * sizeof(int));
	GPUMALLOC((void**)&d_in, dataSize * sizeof(int));
	TOGPU(d_in, in, dataSize*sizeof(int));
	GPUMALLOC((void**)&d_in_len, dataSize * sizeof(int));
	TOGPU(d_in_len, in_len, dataSize*sizeof(int));

	INIT_TIMER;
	START_TIMER;

	gpu_run_length_decoding(d_in, d_in_len, dataSize, d_out, outSize);
	gpu_delta_decoding(d_out, (*outSize));
	
	END_TIMER;
	PRINT_TIMER_SEC("delta + rle decoding kernel: ");

	FROMGPU(out, d_out, sizeof(int)*(*outSize));

	printf("decompressed size: %u\n", *outSize);
	GPUFREE(d_in);
	GPUFREE(d_in_len);
	GPUFREE(d_out);
}

void decompressOutput(FILE* resultFile, FILE* refFile, FILE* fout)
{
	uint32 ReadNum, maxReadNameLength, readLength, querySizeInWord, textLength;
	uint32 *c_queryBuffer;	
	char* titleBuffer; 
	char* queryBuffer;
	uint32 i,j,k, id;
	char query[200];
	char *title;
	int numMis;
	char strand;

	//decompressTitle
	INIT_TIMER;

	fread(&textLength, sizeof(int), 1, resultFile);

	char* ref = (char*)malloc(textLength);
	char c;
	c = fgetc(refFile);
	if(c != '>') {
		ref[0] = c;
		i = 1;
	}
	else {
		while(c != '\n'){
			c = fgetc(refFile);
		}
		i = 0;
	}
	
	while(i < textLength && !feof(refFile)) {
		ref[i] = fgetc(refFile);
		
		if(ref[i] != '\n' && ref[i] != 'n' && ref[i] != 'N') {
			ref[i] = toupper(ref[i]);
			i++;
		}	
	}
	printf("load ref done i = %u\n", i);	
	
	fread(&ReadNum, sizeof(int), 1, resultFile);
	fread(&maxReadNameLength, sizeof(int), 1, resultFile);
	fread(&readLength, sizeof(int), 1, resultFile);

	printf("readNum: %u, readLen: %u, maxReadNameLen: %u, textLength: %u\n", ReadNum, readLength, maxReadNameLength, textLength);

	titleBuffer = (char*) malloc(ReadNum * maxReadNameLength);
	START_TIMER;
	decompressTitle(resultFile, titleBuffer, ReadNum, maxReadNameLength);
	END_TIMER;
	PRINT_TIMER_SEC("decompress title: ");

	//decompressDNA
	querySizeInWord = (readLength + 15) / 16;
	c_queryBuffer = (uint32*) malloc(querySizeInWord * sizeof(int) * ReadNum);
	queryBuffer = (char*)malloc(readLength * ReadNum * 2);

	fread(c_queryBuffer, sizeof(int), querySizeInWord*ReadNum, resultFile);

	START_TIMER;
	decompressDNABuffer(c_queryBuffer, ReadNum, readLength, queryBuffer);
	getReverseBuffer(queryBuffer, ReadNum, readLength, queryBuffer + readLength * ReadNum);
	END_TIMER;
	PRINT_TIMER_SEC("decompress dna: ");

	//decompressOutput
	uint32 numSA, nresult, compressedSize, numQueryId;
	uint32 *c_queryId, *c_queryId_len, *offset, *pos;
	uint32 * queryId;

	fread(&numSA, sizeof(int), 1, resultFile);
	fread(&nresult, sizeof(int), 1, resultFile);
	fread(&compressedSize, sizeof(int), 1, resultFile);
	
	printf("numSA: %u, numAlign: %u compressedSize: %u\n", numSA, nresult, compressedSize);
	numQueryId = numSA;	

	c_queryId = (uint32*)malloc(sizeof(int)*compressedSize);
	c_queryId_len = (uint32*)malloc(sizeof(int)*compressedSize);
	offset = (uint32*)malloc(sizeof(int)*numSA);
	pos = (uint32*)malloc(sizeof(int)*nresult);
	queryId = (uint32*)malloc(sizeof(int)*numSA);

	fread(c_queryId, sizeof(int), compressedSize, resultFile);
	fread(c_queryId_len, sizeof(int), compressedSize, resultFile);
	fread(offset, sizeof(int), numSA, resultFile);
	fread(pos, sizeof(int), nresult, resultFile);

	START_TIMER;
	decompressQueryId(c_queryId, c_queryId_len, compressedSize, queryId, &numQueryId);
	assert(numQueryId == numSA);
	END_TIMER;
	PRINT_TIMER_SEC("decompress queryId: ");

	//find misMatch

	struct Info info;
	
	info.numMisMatch = (uint8*)malloc(numSA);
	info.misPos1 = (uint8*)malloc(numSA);	
	info.misPos2 = (uint8*)malloc(numSA);	
	info.misBase1 = (uint8*)malloc(numSA);	
	info.misBase2 = (uint8*)malloc(numSA);	
	info.readStringLength = readLength * ReadNum * 2;
	info.refStringLength = textLength;
	info.readLength = readLength;
	info.numSA = numSA;

	START_TIMER;
	findMisMatch(queryBuffer, ref, queryId, pos, offset,&info);	
	END_TIMER;
	PRINT_TIMER_SEC("findMisMatch: ");

	k = 0;
	START_TIMER;
	fprintf(fout, "title sequence position strand misNum misPos1 misBase1 misPos2 misBase2\n");

	for(i=0; i<numSA-1; i++) {
		id = queryId[i];
		numMis = info.numMisMatch[i];
		strand = '+';
		if(id >= ReadNum) {
			strand = '-';
			id = id - ReadNum;
		}

		getQuery(queryBuffer, readLength, queryId[i], query);
		title = getTitle(titleBuffer, maxReadNameLength, id);

		if(numMis > 2) {
			printf("numMis > 2! :%u %s %c %d \n",queryId[i],query,strand, numMis);
			break;
		}
		for(j=offset[i]; j<offset[i+1]; j++) {
			fprintf(fout, "%s %s %u %c %d ",title,query, pos[j], strand, numMis);
			if(numMis > 0) 
				fprintf(fout,"%d %c ", info.misPos1[i], info.misBase1[i]);
			if(numMis > 1) 
				fprintf(fout,"%d %c ", info.misPos2[i], info.misBase2[i]);
			fprintf(fout,"\n");
			k++;
		}
	}
	id = queryId[i];
	numMis = info.numMisMatch[i];
	strand = '+';
	if(id >= ReadNum) {
		strand = '-';
		id = id - ReadNum;
	}

	getQuery(queryBuffer, readLength, queryId[i], query);
	title = getTitle(titleBuffer, maxReadNameLength, id);

	if(numMis > 2) {
		printf("numMis > 2! :%u %s %c %d \n", queryId[i],query, strand, numMis);
	}

	for(j=offset[i]; j<nresult; j++) {
		fprintf(fout, "%s %s %u %c %d ",title,query, pos[j], strand, numMis);
		if(numMis > 0) 
			fprintf(fout,"%d %c ", info.misPos1[i], info.misBase1[i]);
		if(numMis > 1) 
			fprintf(fout,"%d %c ", info.misPos2[i], info.misBase2[i]);
		fprintf(fout, "\n");
		k++;
	}
	assert(k == nresult);
	END_TIMER;
	PRINT_TIMER_SEC("write result to disk : ");

	free(titleBuffer);
	free(queryBuffer);
	free(c_queryId);
	free(c_queryId_len);
	free(queryId);
	free(pos);
	free(offset);
	free(info.numMisMatch);
	free(info.misPos1);
	free(info.misPos2);
	free(info.misBase1);
	free(info.misBase2);
}
void safeOpen(FILE** file, char* fileName, const char* mode)
{
	*file = fopen(fileName, mode);
	if((*file) == NULL) {
		printf("cant open file : %s\n", fileName);
	}
}

int main(int argc, char* argv[])
{
	char *resultFileName = argv[1];
	char *refFileName = argv[2];
	char *outputFileName = argv[3];
	
	FILE* resultFile, *refFile, *fout;
	safeOpen(&resultFile, resultFileName, "rb");
	safeOpen(&refFile, refFileName, "r");
	safeOpen(&fout, outputFileName, "w");

	INIT_TIMER;
	START_TIMER;

	decompressOutput(resultFile, refFile, fout);

	END_TIMER;
	printf("\n\n");
	PRINT_TIMER_SEC("decompression time: ");

	fclose(resultFile);
	fclose(refFile);
	fclose(fout);
}









